# Created by 黄景涛
# DATE: 2024/11/8
import time

from selenium.webdriver import ActionChains
from selenium.webdriver.common.by import By
from selenium.common.exceptions import NoSuchElementException, TimeoutException
from selenium.webdriver.remote.webelement import WebElement
from selenium.webdriver.support.wait import WebDriverWait
from selenium.webdriver.support import expected_conditions as EC

from base.kdt_lib.local_mixin import LocalMiXin
import base.kdt_lib.components as components
from utils.log_helper.logger import logger

""" 工单中心 - 查询搜索 """


class Filter(LocalMiXin):
    """ 快捷查询 """

    # loading面版
    loading_loc = '//div[@class="ticket-table-wrapper"]/div/div[@class="bk-loading"]'
    # 工单列表 - 表头
    table_header_loc = '//table/thead'
    # 工单列表 - 列头
    col_name_loc = table_header_loc + '//div[@class="bk-table-header-label"][text()="{col_name}"]/..'
    # 工单列表 - 内容
    table_body_loc = '//table/tbody'
    # 横向滚动条的类名
    scroll_bar_class_name = 'table-scroll-vertical-thumb-w'
    # 工单列表的可视区
    table_scroll_box_loc = f'//div[@class="{scroll_bar_class_name}"]/ancestor::div[@class ="table-scroll-box"]'

    def key_input(self, field: str, *args, instance=None, **kwargs):
        """ 输入条件 """

        # 参数处理
        col_name, component_cls_name = map(lambda item: ''.join(item.split()), field.replace('，', ',').split(','))
        # 查找表头对应的输入框
        input_wrapper = self.get_input_wrapper(col_name)

        # 从组件库获取对应的组件类
        component_cls = getattr(components, component_cls_name, None)
        if component_cls is None:
            msg = f'{component_cls_name}在kdt_lib/components.py中不存在'
            logger.error(msg)
            raise Exception(msg)
        component_inst = component_cls(self.driver)

        # 兼容需要传instance参数的组件
        try:
            component_inst.input_box(input_wrapper, *args, **kwargs, instance=instance)
            self.sleep_loading()
        except TypeError:
            if issubclass(component_cls, components.Text):
                component_inst.input_and_enter(input_wrapper, *args, **kwargs)
            else:
                component_inst.input_box(input_wrapper, *args, **kwargs)
            self.sleep_loading()

    def key_clear(self, col_name: str, component: components.BasicComponent):
        """ 清空列的值 """

        # 输入框
        input_wrapper = self.get_input_wrapper(col_name)

        # hover至输入框，点击清空
        self.hover(input_wrapper)
        clear_icon = input_wrapper.find_element(By.XPATH, component.clear_icon_loc)
        clear_icon.click()
        # 等待接口加载
        self.sleep_loading()

    def sleep_loading(self):
        """ 每输入/清空一次，均需要页面加载等待 """
        self.key_sleep(0.5)
        self.key_sleep_until_unloading(self.loading_loc)

    def hover(self, input_wrapper: WebElement):
        """ 通用方法：hover至输入框 """
        actions = ActionChains(self.driver)
        actions.move_to_element(input_wrapper).perform()

    def get_input_wrapper(self, col_name: str):
        """
        定开：查找工单列表表头，并返回对应的输入框
        :param col_name: 列名称
        :return: 输入框
        """
        table_header = self.driver.find_element(By.XPATH, self.table_header_loc)
        # 查找表头字段
        header_cols = table_header.find_elements(By.XPATH, './/tr/th')

        # 获取列所在的索引
        for idx, col in enumerate(header_cols):
            if col_name in col.accessible_name:
                col_idx = idx
                break
        else:
            raise NoSuchElementException(f'工单列表无【{col_name}】列')

        table_body = self.driver.find_element(By.XPATH, self.table_body_loc)
        # 查找第一行
        body_1th_row = table_body.find_elements(By.XPATH, './/tr[1]/td')

        # 查找第一行中的目标列
        target_td = body_1th_row[col_idx]
        # 横向滚动至可视区
        self.key_horizontal_scroll(col_name)

        # 精确至输入框（非必须）
        input_wrapper = target_td.find_element(By.XPATH, './/div[@class="cell"]/div')

        return input_wrapper

    def key_horizontal_scroll(self, target_col_name: str) -> bool:
        """
        水平滚动
        :param target_col_name: 目标列名
        :return: 返回滚动发现结果
        """
        # 目标列定位
        target_col_loc = self.col_name_loc.format(col_name=target_col_name)
        # 横向滚动条
        scroll_bar = self.driver.find_element(By.CLASS_NAME, self.scroll_bar_class_name)

        # 定义left 值
        max_width, left = self._get_scroll_width()

        action = ActionChains(self.driver)
        found_flag = self._exec_scroll(action, left, max_width, scroll_bar, target_col_loc, self.table_scroll_box_loc)

        if not found_flag:
            logger.warning(f"已滚动至最右侧，未找到【{target_col_name}】列")
            logger.info("从左侧重新滚动查找目标列中...")
            action.drag_and_drop_by_offset(scroll_bar, -max_width, 0).perform()
            # 重置left
            left = 0
            found_flag = self._exec_scroll(action, left, max_width, scroll_bar, target_col_loc,
                                           self.table_scroll_box_loc)
            if not found_flag:
                raise ValueError(f"左右滚动，都未找到【{target_col_name}】列")

        return found_flag

    def _get_scroll_width(self):
        """ 计算横向滚动条的可滚动总距离 """
        # 获取列表的可视区
        table_scroll_box = self.driver.find_element(By.XPATH, self.table_scroll_box_loc)
        # 获取横向滚动条
        scroll_bar = self.driver.find_element(By.CLASS_NAME, self.scroll_bar_class_name)

        # 计算可滚动的总宽度
        diff_width = int(table_scroll_box.size['width'] - scroll_bar.size['width'])
        # 计算已滚动的距离
        rolled_distance = scroll_bar.location['x'] - table_scroll_box.location['x']

        return diff_width, rolled_distance

    def _exec_scroll(self, action, left: int, max_width: int, scroll_bar, target_col_loc, table_scroll_box_loc):
        """ 执行滚动 """

        found_flag = False
        off_x = 50
        # 固定的列，工单列表固定的列有：单号、标题，这里取标题即可
        fixed_col_loc = self.col_name_loc.format(col_name="标题")
        fixed_col = self.driver.find_element(By.XPATH, fixed_col_loc)
        # 列表的可视区
        table_scroll_box = self.driver.find_element(By.XPATH, table_scroll_box_loc)

        # 左边界（标题列右侧）
        left_x = fixed_col.rect['x'] + fixed_col.rect['width']
        # 右边界（工单列表右侧）
        right_x = table_scroll_box.rect['x'] + table_scroll_box.rect['width']

        while left < max_width:
            try:
                WebDriverWait(self.driver, 0.1).until(
                    self.visibility_of_element_located(target_col_loc, left_x, right_x))
                found_flag = True
                break
            except TimeoutException:
                left += off_x
                action.drag_and_drop_by_offset(scroll_bar, off_x, 0).perform()

        return found_flag

    class visibility_of_element_located:
        """ 检查元素可见且未被固定列遮挡 """

        def __init__(self, col_loc: str, left_x: str, right_x: str):
            """
            eg: 工单列表，滚动时，单号、标题列是固定的，其他列会被它们遮挡
            :param col_loc: 目标列
            :param left_x: 可视左边界
            :param right_x: 可视右边界
            :return:
            """
            self.col_loc = col_loc
            self.left_x = left_x
            self.right_x = right_x

        def __call__(self, driver):
            # 目标列对象
            t_col = driver.find_element(By.XPATH, self.col_loc)
            # 比较边界
            t_left_x = t_col.rect['x']
            t_right_x = t_left_x + t_col.rect['width']
            return EC.visibility_of_element_located((By.XPATH, self.col_loc))(driver) \
                and (t_left_x >= self.left_x and t_right_x <= self.right_x)

    def vertical_scroll(self, target_row: WebElement):
        """
        垂直滚动
        :param target_row: 目标行
        :return:
        """


class AdvancedFilter(LocalMiXin):
    """
    高级查询

    特性:
       1、条件组，结构包含：字段名称、运算符、条件值
       2、条件组之间，and/or组合
    """
    ...
